#include "wireformat.h"


namespace apollo
{
	namespace core
	{
		char WireFormat::MAGIC_STRING[7]= "#!X8wz";
		char WireFormat::TERMINATOR[7]= "#!zw8X";

		WireFormat::WireFormat(int readTimeout):readTimeout(readTimeout)
		{
		}

		WireFormat::~WireFormat(void)
		{
		}

		PayloadPtr WireFormat::unblockingRead(QSharedPointer<QAbstractSocket> socket, WireFormat::ReadingContext &ctx)
		{
			qint32 tmp32;
			qint16 tmp16;

			if(ctx.readedStep == WireFormat::Begin)
			{
				//reading margin string and header size
				if(socket->bytesAvailable() < MAGIC_STRING_LENGTH + HEADER_SIZE_FIELD)
				{
					ctx.state = NoAvailabeBytesError;
					return PayloadPtr(NULL);
				}
				char msgleader [7];
				quint16 tmpu16;
				socket->read(msgleader, MAGIC_STRING_LENGTH);
				msgleader[6] = '\0';
				if(strcmp(msgleader, MAGIC_STRING) != 0)
				{
					ctx.state = MagicStringError;
					return PayloadPtr(NULL);
				}
				socket->read(reinterpret_cast<char*>(&tmpu16), HEADER_SIZE_FIELD);
				ctx.headerSize = qFromBigEndian(tmpu16);
				ctx.readedStep = WireFormat::HeaderSize;
				if(ctx.headerSize > MAX_HEADER_SIZE)
				{
					ctx.state = MessageSizeError;
					return PayloadPtr(NULL);
				}

			}

			if(ctx.readedStep == HeaderSize)
			{
				//reading header
				if(socket->bytesAvailable() < ctx.headerSize)
				{
					ctx.state = NoAvailabeBytesError;
					return PayloadPtr(NULL);

				}
				char* data = new char[ctx.headerSize];
				if(socket->read(data, ctx.headerSize) == ctx.headerSize)
				{
					ctx.messageId = qFromBigEndian( *reinterpret_cast<qint32*>(data ));
					ctx.messageType = qFromBigEndian( *reinterpret_cast<qint16*>(data + MESSAGE_ID_FIELD));
					if(ctx.headerSize > MESSAGE_ID_FIELD + MESSAGE_TYPE_FIELD)
					{
						ctx.header = QString::fromUtf8(data  + MESSAGE_ID_FIELD + MESSAGE_TYPE_FIELD, 
							ctx.headerSize - MESSAGE_ID_FIELD - MESSAGE_TYPE_FIELD);
					}
					delete[] data;
				}
				else
				{
					delete[] data;
					ctx.state = NoAvailabeBytesError;
					return PayloadPtr(NULL);
				}

				ctx.readedStep = Header;
			}

			if(ctx.readedStep == Header)
			{
				//read body size;
				if(socket->bytesAvailable() < BODY_SIZE_FIELD)
				{
					ctx.state = NoAvailabeBytesError;
					return PayloadPtr(NULL);
				}

				if(socket->read(reinterpret_cast<char*>(&tmp32), BODY_SIZE_FIELD) == BODY_SIZE_FIELD)
				{
					ctx.bodySize = qFromBigEndian(tmp32);
					ctx.readedStep = WireFormat::BodySize;
				}
				else
				{
					ctx.state = NoAvailabeBytesError;
					return PayloadPtr(NULL);
				}

				if(ctx.bodySize > MAX_BODY_SIZE)
				{
					ctx.state = MessageSizeError;
					return PayloadPtr(NULL);
				}

				if(ctx.bodySize == 0)
				{
					ctx.readedStep = WireFormat::Body;
				}
				else
				{
					ctx.createBody(ctx.bodySize);//create buffer first.
					ctx.currentBodySize = 0; // have read body;
				}
			}

			if(ctx.readedStep == BodySize && socket->bytesAvailable() > 0)
			{
				//read body;

				int readedSize = socket->read(ctx.body + ctx.currentBodySize, ctx.bodySize - ctx.currentBodySize);
				ctx.currentBodySize += readedSize;
				if(ctx.currentBodySize == ctx.bodySize)
				{
					ctx.readedStep = Body;
				}

			}
			if(ctx.readedStep == Body)
			{
				//read checksum
				if(socket->bytesAvailable() < CHECKSUM_FIELD)
				{
					ctx.state = NoAvailabeBytesError;
					return PayloadPtr(NULL);
				}

				socket->read(reinterpret_cast<char*>(&tmp32), CHECKSUM_FIELD);
				ctx.checksum = qFromBigEndian(tmp32);
				ctx.readedStep = WireFormat::Checksum;
				//check checksum;
				qint32 checksum = qChecksum(ctx.body, ctx.bodySize);
				if(ctx.checksum != checksum)
				{
					ctx.state = ChecksumError;
					return PayloadPtr(NULL);
				}
			}

			if(ctx.readedStep == Checksum)
			{
				//read terminator;
				if(socket->bytesAvailable() < TERMINATOR_LENGTH)
				{
					ctx.state = NoAvailabeBytesError;
					return PayloadPtr(NULL);
				}
				char msgleader [7];
				socket->read(msgleader, TERMINATOR_LENGTH);
				msgleader[6] = '\0';
				if(strcmp(msgleader, TERMINATOR) != 0)
				{
					ctx.state = TerminatorError;
					return PayloadPtr(NULL);
				}
				ctx.readedStep = End;
			}

			ctx.state = Successful;
			PayloadPtr payload(new Payload);

			payload->setMessageId(ctx.messageId);
			payload->setMessageType(ctx.messageType);
			payload->setHeader(ctx.header);
			payload->setBodySize(ctx.bodySize);
			payload->setBody(ctx.body);
			ctx.body = NULL;
			ctx.readedStep = Begin;
			return payload;
		}
		bool WireFormat::IsReadReady(QSharedPointer<QAbstractSocket> socket,int length, int timeout)
		{
			int times = timeout / 10 ;

			for(int i = 1; i <= times; i++ )
			{
				if(socket->bytesAvailable() >= length)
				{
					return true;
				}
				socket->waitForReadyRead(10);
			}
			return false;
		}
		WireFormat::ReadState WireFormat::read(QSharedPointer<QAbstractSocket> socket, PayloadPtr payload)
		{
			Q_ASSERT_X(payload.isNull() == false, "WireFormat::read", "No payload");

			if(socket->isValid() == false)
			{
				return InvalidSocket;
			}
			qint32 tmp32;
			qint16 tmp16;


			//reading margin string and header size
			if(IsReadReady( socket, MAGIC_STRING_LENGTH + HEADER_SIZE_FIELD, DEFAULT_READ_TIMEOUT) == false)
			{
				return NoAvailabeBytesError;
			}
			char msgleader [7];
			quint16 tmpu16;
			socket->read(msgleader, MAGIC_STRING_LENGTH);
			msgleader[6] = '\0';
			if(strcmp(msgleader, MAGIC_STRING) != 0)
			{
				return  MagicStringError;

			}
			socket->read(reinterpret_cast<char*>(&tmpu16), HEADER_SIZE_FIELD);
			quint16 headerSize = qFromBigEndian(tmpu16);

			if(headerSize > MAX_HEADER_SIZE)
			{
				return MessageSizeError;
			}

			//reading header
			if(IsReadReady( socket, headerSize,DEFAULT_READ_TIMEOUT) == false)
			{
				return NoAvailabeBytesError;
			}
		
			char* data = new char[headerSize];
			if(socket->read(data, headerSize) == headerSize)
			{
				payload->setMessageId( qFromBigEndian( *reinterpret_cast<qint32*>(data )));
				payload->setMessageType( qFromBigEndian( *reinterpret_cast<qint16*>(data + MESSAGE_ID_FIELD)));
				if(headerSize > MESSAGE_ID_FIELD + MESSAGE_TYPE_FIELD)
				{
					payload->setHeader( QString::fromUtf8(data  + MESSAGE_ID_FIELD + MESSAGE_TYPE_FIELD, 
						headerSize - MESSAGE_ID_FIELD - MESSAGE_TYPE_FIELD));
				}
				delete[] data;
			}
			else
			{
				delete[] data;
				return NoAvailabeBytesError;
			}


			//read body size;
			if(IsReadReady( socket, BODY_SIZE_FIELD, DEFAULT_READ_TIMEOUT) == false)
			{
				return NoAvailabeBytesError;
			}

			socket->read(reinterpret_cast<char*>(&tmp32), BODY_SIZE_FIELD);

			qint32 bodySize = qFromBigEndian(tmp32);
			payload->setBodySize(bodySize);
			if(bodySize > MAX_BODY_SIZE)
			{
				return MessageSizeError;
			}

			if(bodySize > 0)
			{

				if(IsReadReady(socket, bodySize, DEFAULT_READ_TIMEOUT) == false)
				{

					return NoAvailabeBytesError;
				}
				char* body = new char[bodySize];
				if( socket->read(body,bodySize) == bodySize)
				{
					payload->setBody(body);
				}
				else
				{
					delete[] body;
					return NoAvailabeBytesError;
				}

				//read checksum
				if(IsReadReady( socket, CHECKSUM_FIELD, DEFAULT_READ_TIMEOUT) == false)
				{
					return NoAvailabeBytesError;
				}
				socket->read(reinterpret_cast<char*>(&tmp32), CHECKSUM_FIELD);
				qint32 checksum = qFromBigEndian(tmp32);

				//check checksum;
				qint32 calcChecksum = qChecksum(payload->getBody(), bodySize);
				if(calcChecksum != checksum)
				{
					return ChecksumError;

				}

			}

			//read terminator;
			if(IsReadReady( socket, TERMINATOR_LENGTH, DEFAULT_READ_TIMEOUT) == false)
			{
				return NoAvailabeBytesError;
			}
			char msgterminator [7];
			socket->read(msgterminator, TERMINATOR_LENGTH);
			msgterminator[6] = '\0';
			if(strcmp(msgterminator, TERMINATOR) != 0)
			{
				return TerminatorError;
			}

			return Successful;	
		}

		void WireFormat::write(QSharedPointer<QAbstractSocket> socket, PayloadPtr payload)
		{
			qint32 id = payload->getMessageId();
			qint16 messageType = payload->getMessageType();
			QByteArray qheader =  payload->getHeader().toUtf8();
			qint16 headerSize = qheader.size() + MESSAGE_ID_FIELD + MESSAGE_TYPE_FIELD;
			qint32 bodySize = payload->getBodySize();

			const char* body = payload->getBody();
			qint32 checksum = qChecksum(body, bodySize);

			//send header
			std::string data;
			data.reserve(headerSize + MAGIC_STRING_LENGTH + BODY_SIZE_FIELD + bodySize 
				+ (bodySize == 0? 0 : CHECKSUM_FIELD) + TERMINATOR_LENGTH);


			qint32 tmp32;
			qint16 tmp16;
			data.append(MAGIC_STRING, MAGIC_STRING_LENGTH);

			quint16 tmpu16= qToBigEndian(headerSize);
			data.append(reinterpret_cast<char*>( &tmpu16), sizeof(quint16)); //header size, 2 byte
			tmp32 = qToBigEndian(id);
			data.append(reinterpret_cast<char*>( &tmp32), sizeof(qint32));//message id, 4 byte
			tmp16 = qToBigEndian(messageType);
			data.append(reinterpret_cast<char*>( &tmp16), sizeof(qint16)); //message type, 2 bytes.

			if(headerSize > 0)
			{			
				data.append(qheader.constData(), qheader.size());
			}
			//socket->write(data);


			tmp32 = qToBigEndian(bodySize);
			data.append(reinterpret_cast<char*>( &tmp32), sizeof(qint32));//body size, 4 byte
			if(bodySize > 0)
			{
				data.append(body, bodySize);
				tmp32= qToBigEndian(checksum);
				data.append(reinterpret_cast<char*>( &tmp32), sizeof(qint32)); //checksum, 4 bytes

			}

			data.append(TERMINATOR, TERMINATOR_LENGTH);
			socket->write(data.c_str(), data.size());

		}

		void WireFormat::decode(char* data, qint32 totalSize, PayloadPtr payload, quint16 &checksum)
		{/*
		 Q_ASSERT_X(payload.isNull() == false, "WireFormat::decode", "No payload");
		 qint32 id ;
		 qint16 messageType ;
		 QString header ;
		 qint16 headerSize ;
		 qint32 bodySize ;
		 char* body;

		 id = qFromBigEndian( *reinterpret_cast<qint32*>(data ));
		 messageType = qFromBigEndian( *reinterpret_cast<qint16*>(data  + MESSAGE_ID_FIELD));
		 headerSize = qFromBigEndian( *reinterpret_cast<qint16*>(data  + MESSAGE_ID_FIELD + MESSAGE_TYPE_FIELD));
		 if(headerSize > 0)
			{
			header = QString::fromUtf8(data  + MESSAGE_ID_FIELD + MESSAGE_TYPE_FIELD + HEADER_SIZE_FIELD, headerSize);
			}
			bodySize = totalSize - (MESSAGE_ID_FIELD + MESSAGE_TYPE_FIELD + HEADER_SIZE_FIELD + headerSize + CHECKSUM_FIELD);
			if(bodySize > 0)
			{
			body = new char[bodySize];
			memcpy(body, data + MESSAGE_ID_FIELD + MESSAGE_TYPE_FIELD + HEADER_SIZE_FIELD + headerSize, bodySize);
			}
			checksum = qFromBigEndian( *reinterpret_cast<quint16*>(data + totalSize - CHECKSUM_FIELD));

			payload->setMessageId(id);
			payload->setMessageType(messageType);
			payload->setHeader(header);
			payload->setBodySize(bodySize);
			payload->setBody(body);*/
		}

		void WireFormat::encode(std::string & data, PayloadPtr payload)
		{
			//Q_ASSERT_X(payload.isNull() == false, "WireFormat::encode", "No payload");
			//qint32 id = payload->getMessageId();
			//qint16 messageType = payload->getMessageType();
			//QByteArray qheader =  payload->getHeader().toUtf8();
			//qint16 headerSize = qheader.size();
			//qint32 bodySize = payload->getBodySize();

			//const char* body = payload->getBody();
			//qint32 totalSize =  MESSAGE_ID_FIELD + MESSAGE_TYPE_FIELD + HEADER_SIZE_FIELD
			//	+  headerSize + bodySize + CHECKSUM_FIELD; // not include total size field.


			//data.reserve(totalSize + TOTAL_SIZE_FIELD);
			//qint32 tmp32;
			//qint16 tmp16;
			//tmp32 = qToBigEndian(totalSize);
			//data.append(reinterpret_cast<char*>( &tmp32), sizeof(qint32)); //total size, 4 byte
			//tmp32 = qToBigEndian(id);
			//data.append(reinterpret_cast<char*>( &tmp32), sizeof(qint32));//message id, 4 byte
			//tmp16 = qToBigEndian(messageType);
			//data.append(reinterpret_cast<char*>( &tmp16), sizeof(qint16));
			//tmp16 = qToBigEndian(headerSize);
			//data.append(reinterpret_cast<char*>( &tmp16), sizeof(qint16)); //header size, 2 byte
			//if(headerSize > 0)
			//{			
			//	data.append(qheader.constData(), headerSize);
			//}
			//if(bodySize > 0)
			//{
			//	data.append(body, bodySize);
			//}
			//qint16 checksum = qChecksum(data.c_str(), data.size());
			//tmp16 = qToBigEndian(checksum);
			//data.append(reinterpret_cast<char*>( &tmp16), sizeof(qint16));
		}
	}}
